package ua.com.shootgameserver.server;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.SocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;

import javax.annotation.PostConstruct;

import org.springframework.beans.BeansException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.support.AbstractApplicationContext;
import org.springframework.core.env.Environment;
import org.springframework.stereotype.Service;

import ua.com.shootgameserver.configuration.CoreConfig;
import ua.com.shootgameserver.entities.Player;
import ua.com.shootgameserver.exceptions.ServerSocketAcceptException;
import ua.com.shootgameserver.exceptions.ServerSocketCloseException;
import ua.com.shootgameserver.exceptions.ServerSocketException;
import ua.com.shootgameserver.exceptions.ServerSocketWriteException;
import ua.com.shootgameserver.protocol.Data;
import ua.com.shootgameserver.session.SessionPlayer;
import ua.com.shootgameserver.utils.SessionTool;

@Service
public class GameServerImpl implements GameServer {
	private Selector selector;
    private Map<SocketAddress, SessionPlayer> sessionMap;
    private ApplicationContext ctx;

    @Autowired
	private Environment env;

	@PostConstruct
	public void init() {
		try {
			String serverHost = String.valueOf(env.getProperty("ua.com.shootgameserver.serverhost"));
			int serverPort = Integer.valueOf(env.getProperty("ua.com.shootgameserver.serverport"));
			ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
			serverSocketChannel.configureBlocking(false);
			serverSocketChannel.bind(new InetSocketAddress(serverHost,serverPort));
			selector = Selector.open();
			serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
            sessionMap = new ConcurrentHashMap<SocketAddress, SessionPlayer>();
		} catch (IOException e) {
			throw new ServerSocketException(e);
		}
	}

	public void start() {
		for (;;) {
			if (selector.isOpen()) {
				try {
					int keyCount = selector.selectNow();
					if (keyCount > 0) {
						Set<SelectionKey> selectedKeys = selector.selectedKeys();
						Iterator<SelectionKey> keyIterator = selectedKeys.iterator();
						while(keyIterator.hasNext()) {
							SelectionKey key = keyIterator.next();
							if (!key.isValid()) {
								continue;
							}
							if (key.isValid() && key.isAcceptable()) {
								accept(key);
							}
							if (key.isValid() && key.isReadable()) {
								read(key);
							}
							if (key.isValid() && key.isWritable()) {
								write(key);
							}
						}
						keyIterator.remove();
					}
				} catch (IOException e) {
					throw new ServerSocketException(e);
				}
			} else {
				break;
			}
		}
	}
	
	private void accept(SelectionKey sk){
        try {
            ServerSocketChannel serverSocketChannel = (ServerSocketChannel)sk.channel();
            SocketChannel socketChannel = serverSocketChannel.accept();
            socketChannel.configureBlocking(false);

            SocketAddress remoteSocketAddress= socketChannel.socket().getRemoteSocketAddress();
            SelectionKey selectionKey = socketChannel.register(selector, SelectionKey.OP_WRITE);
            SessionPlayer sessionPlayer = null;
            if (!sessionMap.containsKey(remoteSocketAddress)){
                sessionPlayer = ctx.getBean(SessionPlayer.class);
                sessionPlayer.setSocketAddress(remoteSocketAddress);
            } else {
                sessionPlayer = sessionMap.get(remoteSocketAddress);
            }
            selectionKey.attach(sessionPlayer);
        } catch (IOException e) {
            throw new ServerSocketAcceptException(e);
        }

    }
    private void read(SelectionKey sk){
		
	}
    private void write(SelectionKey sk){
    	try {
            SocketChannel socketChannel = (SocketChannel)sk.channel();
            SessionPlayer sessionPlayer = (SessionPlayer)sk.attachment();
            ByteBuffer dataLength = sessionPlayer.getOutgoingData().getDataLength();
            if (dataLength.position()<dataLength.limit()){
                int writeDataLength = socketChannel.write(dataLength);
                if (writeDataLength<=0){
                    closeChannel(sk);
                    return;
                }
                ByteBuffer data = sessionPlayer.getOutgoingData().getData();
                int writeData = socketChannel.write(data);
                if (writeData<=0){
                    closeChannel(sk);
                    return;
                }
            }
        } catch (IOException e) {
            throw new ServerSocketWriteException(e);
        }
	}

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        this.ctx = applicationContext;
    }

    private void closeChannel(SelectionKey sk){
        SocketChannel sc = (SocketChannel)sk.channel();
        sk.cancel();
        try {
            if (sc.isConnected()) {
                sc.close();
            }
        } catch (IOException e) {
            throw new ServerSocketCloseException(e);
        }
    }
}
